package be.dch.guice.utils;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import org.apache.log4j.Logger;

/**
 * Annotation Utility which allows us to find annotated classes.
 */

@SuppressWarnings({ "rawtypes", "unchecked" })
public class AnnotationUtil {
    protected final static Logger logger = Logger.getLogger(AnnotationUtil.class);

    /**
     * Returns all classes within the given package name that are annotated with
     * the given annotation.
     * 
     * @param bindingAnnotation
     *            The binding annotation.
     * @param packageName
     *            The package name.
     * @return annotatedClasses The list of annotated classes.
     */
    public static List<Class> getAnnotatedClasses(final Class bindingAnnotation, final String packageName) {
	final List<Class> annotatedClasses = new ArrayList<Class>();
	for (final Class<?> clazz : AnnotationUtil.getClasses(packageName)) {
	    if (clazz.isAnnotationPresent(bindingAnnotation)) {
		annotatedClasses.add(clazz);
	    }
	}
	return annotatedClasses;
    }

    /**
     * Scans all classes accessible from the context class loader which belong
     * to the given package and subpackages.
     * 
     * @param packageName
     *            The base package
     * @return The classes
     * @throws ClassNotFoundException
     * @throws IOException
     */
    public static List<Class> getClasses(final String packageName) {
	final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	assert classLoader != null;
	Enumeration<URL> resources = null;
	try {
	    resources = classLoader.getResources(packageName.replace('.', '/'));
	} catch (final IOException e) {
	    throw new IllegalArgumentException("Could not find package: " + packageName);
	}
	final List<File> dirs = new ArrayList<File>();
	while (resources.hasMoreElements()) {
	    try {
		dirs.add(new File(URLDecoder.decode(resources.nextElement().getFile(), "UTF-8")));
	    } catch (final UnsupportedEncodingException e) {
		throw new IllegalArgumentException("Could not decode url with encoding: UTF-8");
	    }
	}
	final List<Class> classes = new ArrayList<Class>();
	for (final File directory : dirs) {
	    try {
		classes.addAll(AnnotationUtil.findClasses(directory, packageName));
	    } catch (final ClassNotFoundException e) {
		AnnotationUtil.logger.error(e.getMessage());
	    }
	}
	return classes;
    }

    /**
     * Recursive method used to find all classes in a given directory and
     * subdirs.
     * 
     * @param directory
     *            The base directory
     * @param packageName
     *            The package name for classes found inside the base directory
     * @return The classes
     * @throws ClassNotFoundException
     */
    private static List<Class> findClasses(final File directory, final String packageName) throws ClassNotFoundException {
	final List<Class> classes = new ArrayList<Class>();
	if (!directory.exists()) {
	    return classes;
	}
	final File[] files = directory.listFiles();
	for (final File file : files) {
	    if (file.isDirectory()) {
		assert !file.getName().contains(".");
		classes.addAll(AnnotationUtil.findClasses(file, packageName + "." + file.getName()));
	    } else if (file.getName().endsWith(".class")) {
		classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
	    }
	}
	return classes;
    }
}
